// Protocol Buffers for GWT
// Copyright 2010 Thomas Broyer All rights reserved.
// http://code.google.com/p/protobuf-gwt/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package net.ltgt.gwt.protobuf.compiler.client;

import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.EnumDescriptor;
import com.google.protobuf.Descriptors.FileDescriptor;
import com.google.protobuf.Descriptors.ServiceDescriptor;
import com.google.protobuf.compiler.CodeGenerator;
import com.google.protobuf.compiler.JavaHelpers;

import net.ltgt.gwt.protobuf.compiler.AbstractFileGenerator;
import net.ltgt.gwt.protobuf.compiler.CustomFieldSerializerGenerator;
import net.ltgt.gwt.protobuf.compiler.ServiceAsyncGenerator;
import net.ltgt.gwt.protobuf.compiler.ServiceEmulGenerator;

import java.io.IOException;
import java.io.Writer;
import java.util.List;

/**
 * @author t.broyer@ltgt.net Thomas Broyer
 * Based on the initial work of:
 * @author kenton@google.com Kenton Varda
 * Based on original Protocol Buffers design by
 * Sanjay Ghemawat, Jeff Dean, and others.
 */
public class FileGenerator extends AbstractFileGenerator {

  private final String className;

  public FileGenerator(FileDescriptor file) {
    super(file);
    this.className = JavaHelpers.getFileClassName(file);
  }

  public String getJavaPackage() {
    return javaPackage;
  }

  public String getClassName() {
    return className;
  }

  public void generate(Writer output) throws IOException {
    generateFileHeader(output);

    output.write("public final class " + className + " {\n");
    output.write("  private " + className + "() {}\n");

    // TODO: registerAllExtensions?

    if (JavaHelpers.hasDescriptorMethods(file)) {
      generateDescriptor(output);
    }

    if (!file.getOptions().getJavaMultipleFiles()) {
      for (EnumDescriptor enumType : file.getEnumTypes()) {
        new EnumGenerator(enumType).generate(output);
      }
      for (Descriptor descriptor : file.getMessageTypes()) {
        new MessageGenerator(descriptor).generate(output);
        new CustomFieldSerializerGenerator(descriptor).generate(output);
      }
      if (!file.getOptions().getJavaGenericServices()) {
        for (ServiceDescriptor service : file.getServices()) {
          new ServiceEmulGenerator(service).generate(output);
          new ServiceAsyncGenerator(service).generate(output);
        }
      }
    }

    output.write("\n");
    output.write("public static void internalForceInit() {}\n");

    output.write("\n");
    output.write("// @@protoc_insertion_point(outer_class_scope)\n");
    // outdent
    output.write("}\n");
  }

  public void generateSiblings(String packageDir,
      CodeGenerator.Context context) throws IOException {
    if (file.getOptions().getJavaMultipleFiles()) {
      for (EnumDescriptor enumType : file.getEnumTypes()) {
        generateSibling(new EnumGenerator(enumType), packageDir,
            context);
      }
      for (Descriptor descriptor : file.getMessageTypes()) {
        generateSibling(new MessageGenerator(descriptor), packageDir,
            context);
        generateSibling(new CustomFieldSerializerGenerator(descriptor),
            packageDir, context);
      }
      if (!file.getOptions().getJavaGenericServices()) {
        for (ServiceDescriptor service : file.getServices()) {
          generateSibling(new ServiceEmulGenerator(service), packageDir,
              context);
          generateSibling(new ServiceAsyncGenerator(service), packageDir,
              context);
        }
      }
    }
  }

  private void generateDescriptor(Writer output) throws IOException {
    output.write("public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() {\n");
    output.write("  return Descriptor.INSTANCE;\n");
    output.write("}\n");
    output.write("\n");
    output.write("private static class Descriptor extends com.google.protobuf.Descriptors.FileDescriptor {\n");
    output.write("  static final Descriptor INSTANCE = new Descriptor();\n");
    output.write("\n");
    output.write("  private Descriptor() {\n");
    output.write("    super(\"" + file.getName() + "\", \"" + file.getPackage()
        + "\");\n");
    output.write("  }\n");
    // Messages
    output.write("  protected java.util.List<com.google.protobuf.Descriptors.Descriptor> createMessageTypes() {\n");
    if (file.getMessageTypes().isEmpty()) {
      output.write("    return java.util.Collections.emptyList();\n");
    } else {
      output.write("    return new com.google.protobuf.Descriptors.DescriptorList<\n");
      output.write("        com.google.protobuf.Descriptors.Descriptor>\n");
      output.write("        ( this, " + file.getMessageTypes().size() + ") {\n");
      output.write("      protected com.google.protobuf.Descriptors.Descriptor getDescriptor(int i) {\n");
      output.write("        switch (i) {\n");
      for (Descriptor descriptor : file.getMessageTypes()) {
        output.write("        case " + descriptor.getIndex() + ":\n");
        output.write("          return " + JavaHelpers.getClassName(descriptor)
            + ".getDescriptor();\n");
      }
      output.write("        }\n");
      output.write("        return null;\n");
      output.write("      }\n");
      output.write("    };\n");
    }
    output.write("  }\n");
    // Enums
    output.write("  protected java.util.List<com.google.protobuf.Descriptors.EnumDescriptor> createEnumTypes() {\n");
    if (file.getEnumTypes().isEmpty()) {
      output.write("    return java.util.Collections.emptyList();\n");
    } else {
      output.write("    return new com.google.protobuf.Descriptors.DescriptorList<\n");
      output.write("        com.google.protobuf.Descriptors.EnumDescriptor>\n");
      output.write("        ( this, " + file.getEnumTypes().size() + ") {\n");
      output.write("      protected com.google.protobuf.Descriptors.EnumDescriptor getDescriptor(int i) {\n");
      output.write("        switch (i) {\n");
      for (EnumDescriptor descriptor : file.getEnumTypes()) {
        output.write("        case " + descriptor.getIndex() + ":\n");
        output.write("          return " + JavaHelpers.getClassName(descriptor)
            + ".getDescriptor();\n");
      }
      output.write("        }\n");
      output.write("        return null;\n");
      output.write("      }\n");
      output.write("    };\n");
    }
    output.write("  }\n");
    // Services
    output.write("  protected java.util.List<com.google.protobuf.Descriptors.ServiceDescriptor> createServices() {\n");
    if (file.getOptions().getJavaGenericServices()
        || file.getServices().isEmpty()) {
      output.write("    return java.util.Collections.emptyList();\n");
    } else {
      output.write("    return new com.google.protobuf.Descriptors.DescriptorList<\n");
      output.write("        com.google.protobuf.Descriptors.ServiceDescriptor>\n");
      output.write("        ( this, " + file.getServices().size() + ") {\n");
      output.write("      protected com.google.protobuf.Descriptors.ServiceDescriptor getDescriptor(int i) {\n");
      output.write("        switch (i) {\n");
      for (ServiceDescriptor service : file.getServices()) {
        output.write("        case " + service.getIndex() + ":\n");
        output.write("          return " + JavaHelpers.getClassName(service)
            + ".Descriptor.INSTANCE;\n");
      }
      output.write("        }\n");
      output.write("        return null;\n");
      output.write("      }\n");
      output.write("    };\n");
    }
    output.write("  }\n");
    // Dependencies
    output.write("  protected com.google.protobuf.Descriptors.FileDescriptor[] createDependencies() {\n");
    List<FileDescriptor> dependencies = file.getDependencies();
    output.write("    return new com.google.protobuf.Descriptors.FileDescriptor[] {\n");
    for (int i = 0; i < dependencies.size(); i++) {
      output.write("      "
          + JavaHelpers.getClassName(dependencies.get(i))
          + ".getDescriptor(),\n");
    }
    output.write("    };\n");
    output.write("  }\n");
    output.write("}\n");
  }
}
